package org.finesys.common.log.util;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpUtil;
import lombok.experimental.UtilityClass;
import org.finesys.common.core.util.SpringUtil;
import org.finesys.common.log.config.LogProperties;
import org.finesys.common.log.core.enums.OperateType;
import org.finesys.common.log.trace.util.TraceIdUtil;
import org.finesys.system.api.entity.SysLog;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;

@UtilityClass
public class LogUtil {


    public SysLog getOperateLogBean() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        SysLog operateLogDto = new SysLog();
        operateLogDto.setRemoteAddr(ServletUtil.getClientIP(request));
        operateLogDto.setRequestUri(URLUtil.getPath(request.getRequestURI()));
        operateLogDto.setMethod(request.getMethod());
        operateLogDto.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
        operateLogDto.setParamsData(getParams(request));
        operateLogDto.setServiceId(getServiceId());
        operateLogDto.setLogType(OperateType.OTHER.getValue());
        operateLogDto.setCreateBy(getUserName());
        operateLogDto.setUpdateBy(getUserName());
        operateLogDto.setTraceId(TraceIdUtil.traceId());
        return operateLogDto;
    }

    /**
     * 转换参数
     */
    private String getParams(HttpServletRequest request) {
        //获取脱敏字段
        LogProperties logProperties = SpringUtil.getBean(LogProperties.class);
        Map<String, String[]> parameterMap = MapUtil.removeAny(request.getParameterMap(), ArrayUtil.toArray(logProperties.getExcludeFields(), String.class));
        return HttpUtil.toParams(parameterMap);
    }

    private String getUserName() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return null;
        }
        return authentication.getName();
    }

    private String getServiceId() {
        String serviceId = SpringUtil.getProperty("spring.application.name");
        return StringUtils.hasText(serviceId) ? serviceId : "";
    }

    /**
     * 获取spel 定义的参数值
     *
     * @param context 参数容器
     * @param key     key
     * @param classz  需要返回的类型
     * @param <T>     返回泛型
     * @return 参数值
     */
    public <T> T getValue(EvaluationContext context, String key, Class<T> classz) {
        SpelExpressionParser expressionParser = new SpelExpressionParser();
        Expression expression = expressionParser.parseExpression(key);
        return expression.getValue(classz);
    }

    /**
     * 获取参数容器
     *
     * @param arguments       方法的参数列表
     * @param signatureMethod 被执行的方法体
     * @return 装载参数的容器
     */
    public EvaluationContext getContext(Object[] arguments, Method signatureMethod) {
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(signatureMethod);
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        if (parameterNames == null) {
            return evaluationContext;
        }
        for (int i = 0; i < parameterNames.length; i++) {
            evaluationContext.setVariable(parameterNames[i], arguments[i]);
        }
        return evaluationContext;
    }
}
